const PENDING = 'PENDING'
const FULFILLED = 'FULFILLED'
const REJECTED = 'REJECTED'

const resolvePromise = (promise, x, resolve, reject) => {
	if (promise === x) {
		throw TypeError('error')
	}
	if (['function', 'object'].includes(typeof x)) {
		const then = x.then
		let called = false
		try {
			if (typeof then === 'function') {
				then.call(
					x,
					(y) => {
						if (called) return
						called = true
						resolvePromise(promise, y, resolve, reject)
					},
					(r) => {
						if (called) return
						called = true
						reject(r)
					}
				)
			} else {
				resolve(x)
			}
		} catch (e) {
			if (called) return
			called = true
			reject(e)
		}
	} else {
		resolve(x)
	}
}

class Promise {
	constructor(executor) {
		this.status = PENDING
		this.value = undefined
		this.reason = undefined
		this.onFulfilledCallbacks = []
		this.onRejectedCallbacks = []

		const resolve = (value) => {
			if (this.status === PENDING) {
				this.status = FULFILLED
				this.value = value
				this.onFulfilledCallbacks.forEach((fn) => fn())
			}
		}

		const reject = (reason) => {
			if (this.status === PENDING) {
				this.status = REJECTED
				this.reason = reason
				this.onRejectedCallbacks.forEach((fn) => fn())
			}
		}

		executor(resolve, reject)
	}

	then = (onFulfilled, onRejected) => {
		onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (val) => val
		onRejected =
			typeof onRejected === 'function'
				? onRejected
				: (err) => {
						throw err
				  }

		const promise2 = new Promise((resolve, reject) => {
			if (this.status === FULFILLED) {
				setTimeout(() => {
					try {
						const x = onFulfilled(this.value)
						resolvePromise(promise2, x, resolve, reject)
					} catch (e) {
						reject(e)
					}
				})
			}

			if (this.status === REJECTED) {
				setTimeout(() => {
					try {
						const x = onRejected(this.reason)
						resolvePromise(promise2, x, resolve, reject)
					} catch (e) {
						reject(e)
					}
				})
			}

			if (this.status === PENDING) {
				this.onFulfilledCallbacks.push(() => {
					setTimeout(() => {
						try {
							const x = onFulfilled(this.value)
							resolvePromise(promise2, x, resolve, reject)
						} catch (e) {
							reject(e)
						}
					})
				})

				this.onRejectedCallbacks.push(() => {
					setTimeout(() => {
						try {
							const x = onRejected(this.reason)
							resolvePromise(promise2, x, resolve, reject)
						} catch (e) {
							reject(e)
						}
					})
				})
			}
		})

		return promise2
	}

	catch = (fn) => {
		return this.then(null, fn)
	}

	finally = (fn) => {
		return this.then(fn, fn)
	}

	static all = (arr) => {
		let result = []
		return new Promise((resolve, reject) => {
			let i = 0
			const next = () => {
				arr[i].then((res) => {
					result.push(res)
					i++
					if (i === arr.length) {
						resolve(result)
					} else {
						next()
					}
				})
			}

			next()
		})
	}

	static race = (arr) => {
		return new Promise((resolve, reject) => {
			arr.forEach((item) => {
				item.then((res) => {
					resolve(res)
				})
			})
		})
	}

	static resolve = (value) => {
		return new Promise((resolve, reject) => {
			resolve(value)
		})
	}

	static reject = (reason) => {
		return new Promise((resolve, reject) => {
			reject(reason)
		})
	}
}

let P1 = new Promise((resolve, reject) => {
	setInterval(() => {
		reject("I'm P1")
	}, 1500)
})
let P2 = new Promise((resolve) => {
	setInterval(() => {
		resolve("I'm P2")
	}, 1000)
})

const p = new Promise((resolve, reject) => {
	resolve('哈哈哈')
})

p.then((data) => {
	console.log(data, 111)
	return data
})
	.then((data) => {
		console.log(data, 222)
		throw new Error('呵呵')
	})
	.catch((err) => {
		console.log(err, 333)
		return '嘿嘿'
	})
	.finally((res) => {
		console.log(res, 444)
	})

Promise.race([P1, P2]).then((value) => {
	console.log(value)
})

Promise.all([P1, P2]).then((value) => {
	console.log(value)
})
